/*
 * Copyright (c) 2011 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.api.client.extensions.auth.helpers.oauth2.draft10;

import com.google.api.client.auth.oauth2.draft10.AccessTokenRequest.AuthorizationCodeGrant;
import com.google.api.client.auth.oauth2.draft10.AccessTokenResponse;
import com.google.api.client.auth.oauth2.draft10.AuthorizationRequestUrl;
import com.google.api.client.extensions.auth.helpers.Credential;
import com.google.api.client.extensions.auth.helpers.ThreeLeggedFlow;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.common.base.Preconditions;

import java.io.IOException;

import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.annotations.NotPersistent;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

/**
 * This class is designed to allow the three legged web app flow for OAuth2 to be accomplished
 * easily. When the constructor is called an authorization url is generated from the provided
 * information. The user should be redirected to that url for confirmation. When the code has been
 * received, the flow will request the access and refresh tokens.
 *
 * <p>
 * It is not safe to use one instance of this implementation from multiple threads.
 * </p>
 *
 * @author moshenko@google.com (Jacob Moshenko)
 * @since 1.4
 */
@PersistenceCapable
public class OAuth2ThreeLeggedFlow implements ThreeLeggedFlow {

  @PrimaryKey
  private String userId;

  @Persistent
  private String tokenEndpoint;

  @Persistent
  private String authorizationUrl;

  @Persistent
  private String clientId;

  @Persistent
  private String clientSecret;

  @Persistent
  private String callbackUrl;

  @NotPersistent
  private HttpTransport transport;

  @NotPersistent
  private JsonFactory jsonFactory;

  /**
   * Create the flow object with the information provided and create the authorization url.
   *
   * @param userId Key that will be used to associate this flow object with an end user.
   * @param clientId Used to identify the client server with the token server.
   * @param clientSecret Secret shared between the client server and the token server.
   * @param scope OAuth2 scope or space delimited list of scopes for which we require access.
   * @param callbackUrl Where the authorization should redirect the user to complete the flow.
   * @param authorizationEndpoint Server to which we will redirect the user to grant authorization.
   * @param tokenEndpoint Server with which we will exchange an authorization code for an access
   *        token.
   */
  public OAuth2ThreeLeggedFlow(String userId,
      String clientId,
      String clientSecret,
      String scope,
      String callbackUrl,
      String authorizationEndpoint,
      String tokenEndpoint) {
    this.userId = userId;
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.callbackUrl = callbackUrl;
    this.tokenEndpoint = tokenEndpoint;

    // Create the authorization url that developers can use to request
    // permissions
    // from the user.
    AuthorizationRequestUrl authorizeUrl = new AuthorizationRequestUrl(authorizationEndpoint);
    authorizeUrl.clientId = clientId;
    authorizeUrl.redirectUri = callbackUrl;
    authorizeUrl.scope = scope;
    authorizationUrl = authorizeUrl.build();
  }

  public Credential complete(String authorizationCode) throws IOException {
    Preconditions.checkNotNull(transport, "Must call setHttpTransport before calling complete.");

    // Ask the authorization server for an access and refresh token in exchange
    // for the code generated by the authorization server on behalf of the user
    AuthorizationCodeGrant request = new AuthorizationCodeGrant(transport,
        jsonFactory,
        tokenEndpoint,
        clientId,
        clientSecret,
        authorizationCode,
        callbackUrl);

    AccessTokenResponse response = request.execute();

    // Create the actual access credential that can be used to authenticate
    // requests
    OAuth2Credential cred =
        new OAuth2Credential(userId, response.accessToken, response.refreshToken);
    cred.initializeForRefresh(clientId, clientSecret, tokenEndpoint, jsonFactory, transport);
    return cred;
  }

  public String getAuthorizationUrl() {
    return authorizationUrl;
  }

  public Credential loadCredential(PersistenceManager pm) {
    Preconditions.checkNotNull(
        jsonFactory, "Must call setJsonFactory before calling loadCredential.");
    Preconditions.checkNotNull(transport, "Must call setHttpTransport before calling complete.");

    try {
      OAuth2Credential cred = pm.getObjectById(OAuth2Credential.class, userId);
      cred.initializeForRefresh(clientId, clientSecret, tokenEndpoint, jsonFactory, transport);
      return cred;
    } catch (JDOObjectNotFoundException e) {
      return null;
    }
  }

  public void setHttpTransport(HttpTransport transport) {
    this.transport = Preconditions.checkNotNull(transport);
  }

  public void setJsonFactory(JsonFactory jsonFactory) {
    this.jsonFactory = Preconditions.checkNotNull(jsonFactory);
  }
}
